home *** CD-ROM | disk | FTP | other *** search
/ Amiga Collections: Camelot / Camelot 043 (1989-06)(Swedish User Group of Amiga)(SE)(PD)[WB].zip / Camelot 043 (1989-06)(Swedish User Group of Amiga)(SE)(PD)[WB].adf / qmenu / qmenu.c < prev    next >
C/C++ Source or Header  |  1989-03-08  |  15KB  |  546 lines

  1. /*
  2.  * Quick Menu Package -- An easy way to make simple (but nice) menus.
  3.  *
  4.  * The client programmer will generally use the GenMenu()/FreeMenu() 
  5.  * interface which creates and frees a whole menu strip.  A menu strip
  6.  * -- both the main strip and menu item strips -- is defined by a
  7.  * NewMenu struct.  Each NewMenu struct (defined in qmenu.h) is just a
  8.  * pointer to an array of strings and a pointer to an array of
  9.  * NewMenu's.  The elements of the array of NewMenu's are paired with the
  10.  * strings in the array and represent the sub menus for that string.
  11.  * The string array is terminated with a null pointer.
  12.  *
  13.  * The package tries to do nice things, like automatically formatting
  14.  * the menu strings into blocks with their command keys, and placing
  15.  * markers pointing to subitem stips.  The strings for the menu items
  16.  * can contain special control codes to control the optional 
  17.  * characteristics of items.  Special codes come at the front of item
  18.  * strings and are delimited with a special character (SPECIAL,
  19.  * defined as '!' by default) and are listed briefly below:
  20.  *
  21.  *    c    Checkmark item
  22.  *    t    Checkmark toggle
  23.  *    +    Checkmark checked
  24.  *    b    Highlight box
  25.  *    n    Highlight none
  26.  *    d    Disabled
  27.  *    =C    Set command key to "C"
  28.  *    0101...    Set item exclude mask
  29.  *
  30.  * Any number of 1's and 0's after the special character will set the
  31.  * Mutual exclude bits for that item in the order they occur.  So the
  32.  * first 1 or 0 will set bit 0 of the exclude mask, the second will
  33.  * set bit 1, etc.  Any unset will be 0.
  34.  *
  35.  * Additionally, if the item string is "-", the item will be 
  36.  * a non-selectable horizontal line (a "rule") that can be used to 
  37.  * visually group similar items in the menu strip.  There only need
  38.  * to be NewMenu structs in the array for text menu items, not rules,
  39.  * but don't forget to account for rules when counting MenuItem's
  40.  * since they take a slot.
  41.  *
  42.  *
  43.  * GenMenu() takes a pointer to a NewMenu struct and will create a main
  44.  * Menu strip from the strings in the struct, with menu items from the
  45.  * associated NewMenu's.  It returns a pointer to a Menu struct which is
  46.  * the first element in the list for this strip.
  47.  *
  48.  * GenStrip() takes a pointer to an array of strings and creates a
  49.  * top-level set of Menu structs for the main menu strip.
  50.  *
  51.  * FreeMenu() frees a Menu strip created with either GenMenu() or
  52.  * GenStrip().
  53.  *
  54.  * GenItems() takes a pointer to a NewMenu struct and creates a single
  55.  * list of MenuItem's from the description.  It also takes an X and Y
  56.  * offset for all the elements in the list.
  57.  *
  58.  * FreeMItem() frees structures created by the above function.
  59.  *
  60.  * AttachSubMenu() takes a single MenuItem and a NewMenu struct and
  61.  * attaches the MenuItems created from that NewMenu struct to the
  62.  * parent MenuItem.  The parent should also have been created with
  63.  * GenItems() in order to work correctly.  AttachSubMenu() places a
  64.  * little maker on the parent item to show that it has subitems.  It
  65.  * returns 1 for sucess and 0 for failure.
  66.  *
  67.  * -- WARNING:
  68.  *
  69.  * The client programmer is completely responsible for keeping the
  70.  * menus within the bounds of the screen and for keeping within the
  71.  * limits of Intuition.  The menu structures will not adjust as they
  72.  * hit the borders of the screen (which might be a nice enhancement).
  73.  * The strings in the NewMenu structures cannot be changed or deleted
  74.  * while the Menus are in use, although they can be used to create
  75.  * multiple Menus from the same strings.
  76.  *
  77.  **
  78.  * This code can be used and modified freely.  Do let me know of any
  79.  * improvements or enhancements you may add, and be sure to give credit
  80.  * where credit is due (in the comments) if you redistribute this code.
  81.  *
  82.  *    Stuart Ferguson        1/89
  83.  *    (shf@well.UUCP)
  84.  */
  85. #include <intuition/intuition.h>
  86. #include "qmenu.h"
  87.  
  88. /*
  89.  * Some useful macros for allocating and freeing structures and
  90.  * arrays of structures.
  91.  */
  92. #define NEW(typ)    (typ*)AllocMem((long)sizeof(typ),0L)
  93. #define FREI(p)        FreeMem(p,(long)sizeof(*p))
  94.  
  95. #define NEW_N(typ,n)    (typ*)AllocMem((long)((n)*sizeof(typ)),0L)
  96. #define FREI_N(p,n)    FreeMem(p,(long)((n)*sizeof(*p)))
  97.  
  98. char * AllocMem();
  99.  
  100. /*
  101.  * Definitions for formatting the menus.  Glossary:
  102.  *
  103.  *    TEXTCOLOR    - color of the text items in the menus.
  104.  *    MARKCOLOR    - color of the subitem marker.
  105.  *    RULECOLOR    - color of the horizontal lines in the menus.
  106.  *    RULEHEIGHT    - vertical thickness of the horizontal rules.
  107.  *    RULEGAP        - vertical blank space around the rules.
  108.  *    RULEMARGIN    - horizontal blank space around the rules.
  109.  *    TEXTGAP        - vertical blank space around the text items.
  110.  *    TEXTMARGIN    - horizontal blank space around the text items.
  111.  *    MAINWID        - width of the text font on the main menu.
  112.  *    MAINGAP        - spacing between items on the main menu strip.
  113.  *    SUBINDENT    - overlap between items and their subitems.
  114.  *    SUBDOWN        - vertical shift between items and their subitems.
  115.  */
  116. #define TEXTCOLOR    2
  117. #define MARKCOLOR    3
  118. #define RULECOLOR    0
  119. #define RULEHEIGHT    2
  120. #define RULEGAP        4
  121. #define RULEMARGIN    10
  122. #define TEXTGAP        2
  123. #define TEXTMARGIN    4
  124. #define MAINWID        10
  125. #define MAINGAP        10
  126. #define SUBINDENT    18
  127. #define SUBDOWN        0
  128.  
  129. /*
  130.  * Escape character for special control codes in text items strings.
  131.  */
  132. #define SPECIAL        '!'
  133.  
  134. /*
  135.  * The extern "ta" is set by the client program for
  136.  * the font to use for these menus.
  137.  */
  138. extern struct TextAttr ta;
  139.  
  140. /*
  141.  * Generic templates to use for creating the dynamic menu structures. 
  142.  */
  143. static struct IntuiText
  144.   generic_itext = {TEXTCOLOR, 0, JAM1, TEXTMARGIN/2, TEXTGAP/2, &ta,NULL,NULL},
  145.   generic_smark = {MARKCOLOR, 0, JAM1, 0, TEXTGAP/2, &ta, (UBYTE *) ";", NULL};
  146.  
  147. static struct Menu
  148.   generic_main = {NULL, 0, 0, 0, 10, MENUENABLED, NULL, NULL};
  149.  
  150. static struct MenuItem
  151.   generic_mitem = {
  152.      NULL, 0, 0, 0, 0,
  153.      ITEMTEXT | ITEMENABLED | HIGHCOMP,
  154.      0, NULL, NULL, 0, NULL, 0
  155. };
  156.  
  157. /* Image struct with no imagery for the horizontal lines.
  158.  */
  159. static struct Image
  160.   generic_hrule = {0, 0, 1, RULEHEIGHT, 2, NULL, 0, RULECOLOR, NULL};
  161.  
  162. static struct MenuItem
  163.   generic_hitem = {
  164.      NULL, RULEMARGIN/2, 0, 0, 0,
  165.      ITEMENABLED | HIGHNONE,
  166.      0, NULL, NULL, 0, NULL, 0
  167. };
  168.  
  169.  
  170. /*
  171.  * Takes an array of strings and associated array of NewMenu structs
  172.  * (as a single NewMenu struct) and constructs a menu strip from the
  173.  * descripton.  This is the main high-level call that most clients
  174.  * will make.
  175.  */
  176. struct Menu * GenMenu (nmen)
  177.     register struct NewMenu *nmen;
  178. {
  179.     register short  i, ok, n;
  180.     register struct Menu *mm;
  181.     register struct MenuItem *mi;
  182.  
  183.     /* Count menus to be generated and create top level structure.
  184.      */
  185.     for (n = 0; nmen->str[n]; n++) ;
  186.     mm = GenStrip (nmen->str);
  187.     if (!mm) return NULL;
  188.  
  189.     /* Create the item strip for each main menu and attach to the
  190.      * top level Menu structure.  Any failure causes the whole
  191.      * thing to crumble to dust.
  192.      */
  193.     ok = 1;
  194.     nmen = nmen->submenu;
  195.     for (i = 0; i < n; i++) {
  196.         if (nmen->str) {
  197.             mi = GenItems (nmen, 0L, 0L);
  198.             mm[i].FirstItem = mi;
  199.             ok &= (mi != NULL);
  200.         }
  201.         nmen ++;
  202.     }
  203.     if (!ok) {
  204.         FreeMenu (mm);
  205.         return NULL;
  206.     }
  207.     return mm;
  208. }
  209.  
  210.  
  211. /*
  212.  * Generate a menu strip.  Just creates the top level Menu structures,
  213.  * linked together and intialized.  Takes an array of pointers to
  214.  * strings.
  215.  */
  216. struct Menu * GenStrip (str)
  217.     char **str;
  218. {
  219.     register short  i, x, num;
  220.     register struct Menu *mm, *m;
  221.  
  222.     /*
  223.      * Create enough struct Menu's for the menu strip. 
  224.      */
  225.     for (num = 0; str[num]; num++) ;
  226.     mm = NEW_N (struct Menu, num);
  227.     if (!mm) return NULL;
  228.  
  229.     /*
  230.      * Init the structures using the generic Menu struct as a template.
  231.      * NOTE: the size of the font for these item labels is unknown for
  232.      *       windows on the Workbench screen, so a size should be used
  233.      *       that will work with 60 & 80 column fonts. 
  234.      */
  235.     x = 0;
  236.     for (i = 0; i < num; i++) {
  237.         m = &mm[i];
  238.         *m = generic_main;
  239.         m->LeftEdge = x;
  240.         m->Width = strlen (str[i]) * MAINWID + TEXTMARGIN;
  241.         x += m->Width + MAINGAP;
  242.         m->MenuName = str[i];
  243.         m->NextMenu = m + 1;
  244.     }
  245.     mm[num - 1].NextMenu = NULL;
  246.     return mm;
  247. }
  248.  
  249.  
  250. /*
  251.  * Attach a submenu to a MenuItem.  Takes the parent MenuItem and a
  252.  * NewMenu structure that will be attached as a sub-menu.  
  253.  * Attaches a ";" mark at the end of the parent item and calls
  254.  * GenItems() to create the sub-menu strip.
  255.  */
  256. BOOL AttachSubMenu (mi, nmen)
  257.     register struct MenuItem    *mi;
  258.     struct NewMenu            *nmen;
  259. {
  260.     register struct IntuiText *it;
  261.  
  262.     /* Create an IntuiText with the marker and position it
  263.      * at the right edge of the item.
  264.      */
  265.     if (!(it = NEW (struct IntuiText))) return FALSE;
  266.     *it = generic_smark;
  267.     it->LeftEdge = mi->Width - IntuiTextLength (it) - 2;
  268.  
  269.     /* Create the subitem structure and attach to the main item.
  270.      */
  271.     if (!(mi->SubItem = GenItems
  272.           (nmen, (LONG) (mi->Width - SUBINDENT), (LONG) SUBDOWN))) {
  273.         FREI (it);
  274.         return FALSE;
  275.     }
  276.  
  277.     /* Only if it all worked attach the new text structure.
  278.      */
  279.     ((struct IntuiText *) mi->ItemFill)->NextText = it;
  280.     return TRUE;
  281. }
  282.  
  283.  
  284. /*
  285.  * Takes the given menu text item and skips past the special control
  286.  * codes while adjusting the associated MenuItem appropriately.
  287.  * Special codes are in the form of "!x", where "x" is:
  288.  *
  289.  *    c    Checkmark item
  290.  *    t    Checkmark toggle
  291.  *    b    Highlight box
  292.  *    n    Highlight none
  293.  *    d    Disabled
  294.  *    +    Checkmark checked
  295.  *    =C    Set command key to "C"
  296.  *    1010...    Set item exclude mask
  297.  *
  298.  * Takes the mostly defined MenuItem and diddles it.  Returns a pointer
  299.  * to the item text with control codes stripped.
  300.  */
  301. static UBYTE * ProcessSpecialStuff (str, mi)
  302.     char *str;
  303.     struct MenuItem *mi;
  304. {
  305.     register LONG    x;
  306.     register int    i;
  307.  
  308.     while (*str == SPECIAL) {
  309.         switch (*++str) {
  310.             case 'c':
  311.             mi->Flags |= CHECKIT;
  312.             break;
  313.             case 't':
  314.             mi->Flags |= CHECKIT | MENUTOGGLE;
  315.             break;
  316.             case 'b':
  317.             mi->Flags &= ~HIGHFLAGS;
  318.             mi->Flags |= HIGHBOX;
  319.             break;
  320.             case 'n':
  321.             mi->Flags &= ~HIGHFLAGS;
  322.             mi->Flags |= HIGHNONE;
  323.             break;
  324.             case 'd':
  325.             mi->Flags &= ~ITEMENABLED;
  326.             break;
  327.             case '+':
  328.             mi->Flags |= CHECKIT | CHECKED;
  329.             break;
  330.             case '=':
  331.             mi->Flags |= COMMSEQ;
  332.             mi->Command = *++str;
  333.             break;
  334.             case '0':
  335.             case '1':
  336.             x = 0;
  337.             i = 0;
  338.             while (*str == '0' || *str == '1')
  339.                 x += (*str++ - '0') << (i++);
  340.             mi->MutualExclude = x;
  341.             str--;
  342.             break;
  343.         }
  344.         str++;
  345.     }
  346.     return (UBYTE*) str;
  347. }
  348.  
  349.  
  350. /*
  351.  * Construct a basic item list for a menu.  Takes a NewMenu structure
  352.  * which contains a pointer to an array of pointers to strings and a 
  353.  * pointer to an array of NewMenu structures.  The strings contain the
  354.  * item text for each menu plus optional special control codes.  If the
  355.  * string is "-", the item will be a horizontal rule rather than a text
  356.  * item.  The NewMenu structures, if not NULL, are the sub-menu's for
  357.  * each menu in the array.
  358.  * "x" and "y" are the horizontal and vertical offsets of this set of
  359.  * MenuItems.  These are set by AttachSubMenu() for positioning submenus
  360.  * under their parent items.
  361.  */
  362. struct MenuItem *GenItems (nmen, x, y)
  363.     struct NewMenu *nmen;
  364.     LONG    x, y;
  365. {
  366.     register struct MenuItem *mi, *cmi;
  367.     register struct IntuiText *itext;
  368.     struct Image   *img;
  369.     register short  i, len, max;
  370.     short           n;
  371.     struct NewMenu *sub;
  372.  
  373.     /* Count menu items (n) and allocate an array for the strip.
  374.      */
  375.     for (n = 0; nmen->str[n]; n++) ;
  376.     if (!(mi = NEW_N (struct MenuItem, n))) return NULL;
  377.  
  378.     /* Counts the number of rules in the menu ("-" strings)
  379.      * and allocates the structures for the lines and the text items.
  380.      */
  381.     max = 0;
  382.     for (i = 0; i < n; i++) max += (*nmen->str[i] == '-');
  383.     if (n - max)
  384.         if (!(itext = NEW_N (struct IntuiText, n - max))) {
  385.             FREI_N (mi, n);
  386.             return NULL;
  387.         }
  388.     if (max)
  389.         if (!(img = NEW_N (struct Image, max))) {
  390.             FREI_N (mi, n);
  391.             if (n - max) FREI_N (itext, n - max);
  392.             return NULL;
  393.         }
  394.  
  395.     /* Loop through text menu items and initialize the
  396.      * associated IntuiText structures.  Compute the maximum
  397.      * width of the menu taking command keys into account while
  398.      * assigning all the other parts of the text MenuItem's.
  399.      */
  400.     max = 0;
  401.     for (i = 0; i < n; i++) {
  402.         if (*nmen->str[i] == '-') continue;    /* skip rules */
  403.  
  404.         /* Init the text MenuItem to point to the assocd IntuiText.
  405.          */
  406.         cmi = &mi[i];
  407.         *cmi = generic_mitem;
  408.         cmi->ItemFill = (APTR) itext;
  409.  
  410.         /* Init the IntuiText and adjust the MenuItem from the
  411.          * flags set in the menu text string.
  412.          */
  413.         *itext = generic_itext;
  414.         itext->IText = ProcessSpecialStuff (nmen->str[i], cmi);
  415.  
  416.         /* Make a first cut at measuring the length of the item.
  417.          */
  418.         len = IntuiTextLength (itext) + TEXTMARGIN;
  419.  
  420.         /* If command key set, add to length.
  421.          */
  422.         if (cmi->Flags & COMMSEQ) len += COMMWIDTH + MAINWID;
  423.  
  424.         /* If this is a checkmark item, shift the text over to
  425.          * make room and add that to the length.
  426.          * Compute the max length.
  427.          */
  428.         if (cmi->Flags & CHECKIT) {
  429.             itext->LeftEdge += CHECKWIDTH;
  430.             len += CHECKWIDTH;
  431.         }
  432.         if (len > max) max = len;
  433.         itext ++;
  434.     }
  435.  
  436.     /* Secondary assignment loop.  Position the text MenuItems and
  437.      * init the horizontal lines.
  438.      */
  439.     for (i = 0; i < n; i++) {
  440.         cmi = &mi[i];
  441.  
  442.         if (*nmen->str[i] != '-') {
  443.             cmi->LeftEdge = x;
  444.             cmi->TopEdge = y;
  445.             cmi->Width = max;
  446.             cmi->Height = ta.ta_YSize + TEXTGAP;
  447.             y += cmi->Height;
  448.         } else {
  449.  
  450.             /* Rule items point to their Image structure
  451.              * and are just a little narrower than the
  452.              * menu itself.
  453.              */
  454.             *img = generic_hrule;
  455.             img->Width = max - RULEMARGIN;
  456.             *cmi = generic_hitem;
  457.             cmi->TopEdge = y + RULEGAP/2;
  458.             y += RULEHEIGHT + RULEGAP;
  459.             cmi->ItemFill = (APTR) img;
  460.             img ++;
  461.         }
  462.         cmi->NextItem = cmi + 1;
  463.     }
  464.     mi[n - 1].NextItem = NULL;
  465.  
  466.     /* Attach submenu's, if any.
  467.      */
  468.     if (!(sub = nmen->submenu)) return mi;
  469.  
  470.     /* Use "max" as a flag for the success of the attachments.
  471.      */
  472.     max = 1;
  473.     for (i = 0; i < n; i++) {
  474.         if (*nmen->str[i] == '-') continue;
  475.         if (sub->str)
  476.             max &= AttachSubMenu (&mi[i], sub);
  477.         sub ++;
  478.     }
  479.     if (!max) {
  480.         FreeMItem (mi);
  481.         return NULL;
  482.     }
  483.     return mi;
  484. }
  485.  
  486.  
  487. /*
  488.  * Free a Menu structure created by GenStrip() that has items 
  489.  * created with GenItems().
  490.  */
  491. void FreeMenu (mm)
  492.     struct Menu    *mm;
  493. {
  494.     register short  i;
  495.     register struct Menu *t;
  496.  
  497.     i = 0;
  498.     for (t = mm; t; t = t->NextMenu) {
  499.         if (t->FirstItem) FreeMItem (t->FirstItem);
  500.         i++;
  501.     }
  502.     FREI_N (mm, i);
  503. }
  504.  
  505.  
  506. /*
  507.  * Free a MenuItem structure created by GenItems().
  508.  */
  509. void FreeMItem (mi)
  510.     register struct MenuItem *mi;
  511. {
  512.     register short  nit, nimg;
  513.     register struct MenuItem *c;
  514.     register struct IntuiText *it = NULL, *it1;
  515.     struct Image   *img = NULL;
  516.  
  517.     /* Scan the MenuItem structures and count the number of images
  518.      * and IntuiText structures.  Find the pointer to the first of
  519.      * each structure in the set.  That will be the first element
  520.      * in the array that was allocated as a unit.
  521.      */
  522.     nit = nimg = 0;
  523.     for (c = mi; c; c = c->NextItem) {
  524.         if (c->SubItem) FreeMItem (c->SubItem);
  525.         if (c->Flags & ITEMTEXT) {
  526.             it1 = (struct IntuiText *) c->ItemFill;
  527.             if (!it) it = it1;
  528.             nit++;
  529.  
  530.             /* Free the subitem marker, if any.
  531.              */
  532.             if (it1->NextText) FREI (it1->NextText);
  533.         } else {
  534.             if (!img) img = (struct Image *) c->ItemFill;
  535.             nimg++;
  536.         }
  537.     }
  538.  
  539.     /* Free the arrays of structures of images and texts, as
  540.      * well as the main array of MenuItem structures themselves.
  541.      */
  542.     if (nit) FREI_N (it, nit);
  543.     if (nimg) FREI_N (img, nimg);
  544.     FREI_N (mi, nit + nimg);
  545. }
  546.